package com.theorydance.esoperator.utils;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import cn.hutool.core.util.StrUtil;

/**
 * 默认使用的es地址"http://127.0.0.1:9200/"，如果需要设置其他地址，可以通过setEsHost进行修改
 */
public class EsUtils {

	/**
	 * es连接地址，默认值为"http://127.0.0.1:9200/"
	 */
    private static String esHost = "http://127.0.0.1:9200/";
	private final static String COMMA_SEPARATOR = ",";
	private final static String DOT_SEPARATOR = ".";
	private final static String ARROW_SEPARATOR = "->";
	private final static String EMPTY_STRING = "";

    /**
     * @param index  索引
     * @param dsl    kibana中的命令，类似SQL，里面可以含有${}， #{}参数，示例<br>
     *               {<br>
     *               &emsp;"query": {<br>
     *               &emsp;&emsp;"match": {<br>
     *               &emsp;&emsp;&emsp;"project": #{modelSMS}<br>
     *               &emsp;&emsp;}<br>
     *               &emsp;},<br>
     *               &emsp;"sort": [<br>
     *               &emsp;&emsp;{<br>
     *               &emsp;&emsp;&emsp;"@timestamp": {<br>
     *               &emsp;&emsp;&emsp;&emsp;"order": "desc"<br>
     *               &emsp;&emsp;&emsp;}<br>
     *               &emsp;&emsp;}<br>
     *               &emsp;],<br>
     *               &emsp;"size": #{size}<br>
     *               }
     * @param params 设置dsl中的参数值
     * @return
     */
    public static List<Map<String, Object>> getEsAggsData(String index, String dsl, String expression, Map<String, Object> params, Map<String,Object> connMap) {
        try {
            String completeDsl = getCompleteDsl(dsl, params);
            String body = Tools.tryDo(() -> {
            	String res = send(esHost + index + "/_search", completeDsl, connMap);
            	if(res == null) {
            		throw new Exception("未获取到数据");
            	}
            	return res;
            });
            com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSONObject.parseObject(body);
            List<Map<String, Object>> list = new ArrayList<>();
            getJSONData(jsonObject, expression, list);
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

	/**
	 * 针对json数据对象，按照表达式expression，将里面的数据提取到集合list中
	 * @param json 要解析的数据对象
	 * @param expression 提取值的规则表达式，如aggregations.carno_maxgpstime.buckets[].gps_time_max.hits.hits[0]._source->gps_time,car_no,latitude,longitude
	 *            要求：(1)表达式中一定存在 -> 进行左右分割，右边为最终需要提取到的属性值; (2)[]代表数组，有数字代表取特定下标的值，没数据代表数组中的数据全取；
	 * @param list 存储解析后的数据
	 * @return
	 */
    public static void getJSONData(com.alibaba.fastjson.JSONObject json, String expression, final List<Map<String, Object>> list) {
		// 表达式校验不通过，不执行后续逻辑
		if (!checkExpression(expression)) return;

		// 存放表达式中的第一个属性
		String attr = null;

		// 存放剔除第一个属性之后剩下的表达式
		String nextExpression = null;

		String expressionLeft = expression.split(ARROW_SEPARATOR)[0];
		// 判断"->"之前的字符串中是否包含字符"."
		if (expressionLeft.contains(DOT_SEPARATOR)) {
			attr = expression.substring(0, expression.indexOf(DOT_SEPARATOR));
			nextExpression = expression.substring(expression.indexOf(DOT_SEPARATOR) + 1);
		} else {
			// 进入该分支有两种可能一种是左侧有属性，一种是左侧为空字符串
			attr = expressionLeft;
			nextExpression = expression.substring(expression.indexOf(ARROW_SEPARATOR));
		}

		// 这个方法是在进行递归调用，当满足这个条件时候，表达式形态应该是"->gps_time,car_no,latitude,longitude"
		if(EMPTY_STRING.equals(attr)) {
			// 去掉最前面的 "->"
			String tempStr = nextExpression.replace(ARROW_SEPARATOR, EMPTY_STRING);
			String[] sourceAttr = tempStr.split(COMMA_SEPARATOR);

			Map<String, Object> map = new HashMap<>(16);
			if(sourceAttr.length==1 && "*".equals(sourceAttr[0])) {
				map.putAll(json);
			}else {
				for (final String x : sourceAttr) {
					String alias = null;
					Object value = null;
					if(x.contains(" as ")) {
						alias = x.split("\\sas\\s")[1].trim();
						// 此处的t可能是多级，也可能是数据的具体下表，也可能是一个对象
						String t = x.split("\\sas\\s")[0].trim();
						value = getAttrValue(json, t);
					}else {
						alias = x.trim();
						value = getAttrValue(json, x.trim());
					}

					value = (value instanceof Date) ? Tools.time((Date)value, null) : value;

					if(net.sf.json.JSONObject.fromObject(null).equals(value)) {
						value = null;
					}
					map.put(alias, value);
				}
			}
			list.add(map);
		}
		// 当前获取的属性是数组
		else if(isArray(attr)) {
			String attrName = attr.split("\\[")[0];
			Integer indexNum = getIndexNum(attr);
			// 为[],进行遍历
			if(indexNum == null) {
				com.alibaba.fastjson.JSONArray array = json.getJSONArray(attrName);
				for (Object object : array) {
					getJSONData((com.alibaba.fastjson.JSONObject)object, nextExpression, list);
				}
			}else { // [Num]
				com.alibaba.fastjson.JSONObject son = json.getJSONArray(attrName).getJSONObject(indexNum);
				getJSONData(son, nextExpression, list);
			}
		}
		// 当前获取的属性是一个普通对象
		else {
			com.alibaba.fastjson.JSONObject son = json.getJSONObject(attr);
			getJSONData(son, nextExpression, list);
		}
    }

	/**
	 * 校验表达式是否合法，合法返回true，不合法返回false
	 * @Author grand_ranfs
	 * @Date 2023/11/17 9:09
	 * @param: expression
	 * @return: boolean
	 */
	private static boolean checkExpression(String expression) {
		// 表达式不能为null
		if (StrUtil.isEmpty(expression)) return false;
		// 表达式必须包含 ->
		if (!expression.contains(ARROW_SEPARATOR)) return false;
		// -> 右边必须有内容
		String[] split = expression.split(ARROW_SEPARATOR);
		if (split.length != 2) return false;
		return true;
	}

    /**
     * 多级方式获取结果，取出来的是一个对象
     * @param json 不能为null
     * @param key 可能是多级方式，也可能含有[下标]方式
     * @return
     */
	private static Object getAttrValue(com.alibaba.fastjson.JSONObject json, final String key) {
		// 形如："attr1"单个属性，且不会存在数组操作
		if(!key.contains(DOT_SEPARATOR)&&!key.contains("[")) {
			return json.get(key);
		}
		List<Map<String, Object>> list = new ArrayList<>();
		// 形如： "任意.attr1"，后缀不是数组的，将其改变为"任意->attr1"带入getJSONData中操作，只取第一条
		if(!key.endsWith("]")) {
			int i = key.lastIndexOf(DOT_SEPARATOR);
			String resultName = key.substring(i+1);
			String index = key.substring(0, i);
			getJSONData(json, index + ARROW_SEPARATOR + resultName, list);
			if(list.size()>0) {
				return list.get(0).get(resultName);
			}
		}
		// 后缀是数组的，"任意.attr1[]" 或 "任意.attr1[2]"
		else {
			int i = key.lastIndexOf(DOT_SEPARATOR);
			String resultName = i > -1 ? key.substring(i+1) : key;
			Integer indexNum = getIndexNum(resultName);
			String index = i > -1 ? key.substring(0, i) : EMPTY_STRING;

			resultName = resultName.replaceAll("\\[\\w*?\\]", EMPTY_STRING);
			getJSONData(json, index + ARROW_SEPARATOR + resultName, list);
			if(list.size()>0) {
				Object obj = list.get(0).get(resultName);
				return indexNum!=null ? net.sf.json.JSONArray.fromObject(obj).get(indexNum) : list.get(0).get(resultName);
			}
		}
		return null;
	}

	/**
     * 判断表达式是否时数组表达式
     */
    public static boolean isArray(String expression) {
    	if(expression.contains("[")) {
    		return true;
    	}
    	return false;
    }

    public static Integer getIndexNum(String arrayExpression) {
        Pattern p = Pattern.compile(".*\\[([0-9]+)\\]");
        Matcher m = p.matcher(arrayExpression);
        boolean b = m.find();
        if(b){
        	return Integer.parseInt(m.group(1));
        }
        return null;
    }

    /**
     * 这里使用原始的http请求连接，没有使用http相关的工具包（比如hutool），是因为在基础模块中可能没有相关依赖，直接使用java自带的
     */
    public static String send(String url, String body, Map<String, Object> connMap) {
    	if(!Tools.isNetUrl(url)) {
    		url = esHost + url;
    	}
    	
        int connectTimeout = 0;
        int readTimeout = 0;
        if (connMap != null) {
            connectTimeout = Tools.objToint(connMap.get("connectTimeout"));
            readTimeout = Tools.objToint(connMap.get("readTimeout"));
        }
        
        return Tools.httpPostJSON(url, body, connectTimeout, readTimeout);
    }

    private static String getCompleteDsl(String dsl, Map<String, Object> params) throws Exception {
        Set<String> keys = Tools.findParams(dsl);
        for (String key : keys) {
			// 使用正则表达式，将"#","$","{","}"四种字符替换为""
            String mapKey = key.replaceAll("[\\$#\\{\\}]", "");
            Object value = params.get(mapKey);
            if (value == null) {
                throw new Exception("没有对应的参数，请设置");
            }
            if (key.startsWith("$")) {
                dsl = dsl.replace(key, value.toString());
            } else {
                // 如果有其他类型，自己在这里继续扩展
                if (value instanceof Integer ||
                        value instanceof Float ||
                        value instanceof Double) {
                    dsl = dsl.replace(key, value.toString());
                } else { // 非数字就以字符串处理
                    dsl = dsl.replace(key, "\"" + value + "\"");
                }
            }
        }
        return dsl;
    }

    public static void setEsHost(String esHost) {
        EsUtils.esHost = esHost;
    }
}
